package cn.lingyangwl.agile.gateway.service;

import cn.hutool.core.net.url.UrlBuilder;
import cn.lingyangwl.agile.dynamic.config.core.*;
import cn.lingyangwl.agile.dynamic.config.model.*;
import cn.lingyangwl.agile.gateway.handler.*;
import cn.lingyangwl.agile.model.constants.*;
import cn.lingyangwl.agile.model.enums.*;
import cn.lingyangwl.agile.model.module.config.*;
import cn.lingyangwl.agile.model.module.config.param.*;
import cn.lingyangwl.agile.model.module.tenant.*;
import cn.lingyangwl.agile.model.utils.*;
import cn.lingyangwl.framework.tool.core.*;
import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author shenguangyang
 */
@Slf4j
@Service
public class FileServiceImpl implements FileService, IConfigListener {

    /**
     * 文件访问url业务对象, 存储在数据库中的文件路径baseUrl格式为 http://业务编码.存储桶.file
     * 桶名就是文件的根路径
     *
     * 该正则含义: 获取 http://[...]file 之间的内容, 包含 http:// 和 file
     */
    private static final String REG = "http://.*?.file";
    private static final Pattern PATTERN = Pattern.compile(REG);

    /**
     * 获取完整url的正则表达式
     */
    private static final String GET_FULL_URL_REGEX = "(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]";
    private static final Pattern GET_FULL_URL_PATTERN = Pattern.compile(GET_FULL_URL_REGEX);

    /**
     * key: tenantId
     */
    public static final Map<String, StorageParam<?>> storageParamCache = new ConcurrentHashMap<>();

    @Resource
    private ConfigFactory configFactory;

    @Override
    public String virtualToRealBaseUrl(ServerHttpRequest request, String respTenantId, String data) {
        String originEndpoint = Optional.ofNullable(request.getHeaders().getFirst(RequestCons.Headers.ORIGIN_ENDPOINT)).orElse("");
        String mainTenantId = Optional.ofNullable(request.getHeaders().getFirst(RequestCons.Headers.MAIN_TENANT_ID)).orElse("-1");
        String tenantId = Optional.ofNullable(request.getHeaders().getFirst(RequestCons.Headers.TENANT_ID)).orElse("-1");

        // 如果响应租户id不为空, 则优先使用响应头中的租户id, 这样可以对平台维护的文件地址进行解析, 否则会出现文件访问不了的情况
        mainTenantId = StringUtils.firstNonEmpty(respTenantId, mainTenantId);
        tenantId = StringUtils.firstNonEmpty(respTenantId, tenantId);
        try {
            TenantContext.get().setMainTenantId(mainTenantId);
            TenantContext.get().setTenantId(tenantId);
            if (StringUtils.isEmpty(mainTenantId)) {
                log.warn("has not will file url to real from virtual, [ tenantId is empty ]");
                return data;
            }
            return this.virtualToRealBaseUrl(mainTenantId, data, originEndpoint);
        } finally {
            TenantContext.clear();
        }

    }

    @Override
    public String realToVirtualBaseUrl(ServerHttpRequest request, String data) {
        String originEndpoint = Optional.ofNullable(request.getHeaders().getFirst(RequestCons.Headers.ORIGIN_ENDPOINT)).orElse("");
        String mainTenantId = Optional.ofNullable(request.getHeaders().getFirst(RequestCons.Headers.MAIN_TENANT_ID)).orElse("-1");
        String tenantId = Optional.ofNullable(request.getHeaders().getFirst(RequestCons.Headers.TENANT_ID)).orElse("-1");
        try {
            TenantContext.get().setMainTenantId(mainTenantId);
            TenantContext.get().setTenantId(tenantId);
            if (StringUtils.isEmpty(mainTenantId)) {
                log.warn("has not will file url to virtual from real, [ tenantId is empty ]");
                return data;
            }
            return this.realToVirtualBaseUrl(mainTenantId, data, originEndpoint);
        } finally {
            TenantContext.clear();
        }
    }

    /**
     * 真实baseUrl替换成成虚拟baseUrl, 一般用于请求数据
     *
     * 替换被上传的文件baseUrl, 在一些需要保存上传的文件路径的接口中, 由于在响应的时候, 网关会将 http://业务编码.桶名.file 前缀
     * 替换成真实的baseUrl, 但是前端在编辑表单的时候, 如果没有修改上传的文件, 直接点保存, 这时候需要将真实的地址再替换成 http://业务编码.桶名.file
     */
    public String realToVirtualBaseUrl(String tenantId, String data, String originEndpoint) {
        // 添加当前租户id到url参数中, 可以解决在管理端的文件地址数据, 租户使用时访问不了问题

        StorageParam<?> storageParam = storageParamCache.get(tenantId);
        if (Objects.isNull(storageParam)) {
            storageParam = (StorageParam<?>) createStorageParam();
        }
        StorageParam.Domain domain = getDomain(tenantId, originEndpoint, storageParam);
        if (Objects.isNull(domain)) {
            return data;
        }

        AbstractFileHandler instance = getFileHandler(storageParam);
        if (Objects.isNull(instance)) {
            return data;
        }

        return instance.realToVirtualBaseUrl(data, storageParam, domain.getDomain());
    }

    private StorageParam.Domain getDomain(String tenantId, String originEndpoint, StorageParam<?> storageParam) {
        if (Objects.isNull(storageParam) || CollectionUtils.isEmpty(storageParam.getDomains())) {
            log.warn("未配置文件存储器配置参数, tenantId: {}", tenantId);
            return null;
        }

        // 获取映射的域名
        StorageParam.Domain domain = storageParam.getDomains().stream()
                .filter(e -> e.getEndpointValue().equalsIgnoreCase(originEndpoint)).findFirst().orElse(null);
        if (Objects.isNull(domain)) {
            log.warn("租户尚未配置文件存储域名, tenantId: {}, originEndpoint: {}", tenantId, originEndpoint);
            return null;
        }
        return domain;
    }

    private AbstractFileHandler getFileHandler(StorageParam<?> storageParam) {
        // 校验存储类型
        StorageTypeEnum storageType = StorageTypeEnum.ofByBindConfig(storageParam.configKey());
        if (Objects.isNull(storageType)) {
            log.warn("storage type code [{}] 没有定义", storageParam.configKey());
            return null;
        }

        // 校验文件处理器
        AbstractFileHandler instance = AbstractFileHandler.getInstance(storageType);
        if (Objects.isNull(instance)) {
            log.warn("未实现类型为 [{}] 的文件处理器", storageType.name());
            return null;
        }
        return instance;
    }

    /**
     * 虚拟baseUrl替换成成真实baseUrl, 一般用于响应数据
     * @param respData 响应数据
     * @param originEndpoint 请求头中 {@link RequestCons.Headers#ORIGIN_ENDPOINT} 的值
     * @return 替换结果
     */
    public String virtualToRealBaseUrl(String tenantId, String respData, String originEndpoint) {
        if (StringUtils.isEmpty(originEndpoint)) {
            log.warn("request header 中没有携带 [{}]", RequestCons.Headers.ORIGIN_ENDPOINT);
            return respData;
        }
        Matcher matcher = PATTERN.matcher(respData.replace("http://127.0.0.1", "").replace("http://localhost", ""));
        String virtualBaseUrl = "";
        // 这里只需要查找第一个即可, 因为正常的业务接口如果需要上传文件, 无论上传多少个文件都会存储到一个存储厂商(minio/oss)中且也会存储到
        // 同一个桶中
        if( matcher.find() ){
            virtualBaseUrl = matcher.group();
        }
        if (StringUtils.isEmpty(virtualBaseUrl)) {
            return respData;
        }

        // 获取业务编码并获取配置
        try {
            String[] split = virtualBaseUrl
                    .replace("http://", "")
                    .replace(".file", "")
                    .split("\\.");
            if (split.length != 2) {
                return respData;
            }
            String bucketName = split[1];

            tenantId = StringUtils.firstNonEmpty(getTenantIdFromData(respData), tenantId);
            // 获取配置
            StorageParam<?> storageParam = storageParamCache.get(tenantId);
            if (storageParam == null) {
                storageParam = (StorageParam<?>) createStorageParam();
            }
            StorageParam.Domain domain = getDomain(tenantId, originEndpoint, storageParam);
            if (Objects.isNull(domain)) {
                return respData;
            }

            // 校验存储类型
            AbstractFileHandler instance = getFileHandler(storageParam);
            if (Objects.isNull(instance)) {
                return respData;
            }

            return respData.replace(virtualBaseUrl, instance.getAccessUrl(bucketName, domain));
        } catch (Exception e) {
            return respData;
        }
    }

    public BaseConfigParam<?> createStorageParam() {
        ConfigInfo config = configFactory.getConfig(ConfigDefineEnum.STORAGE);
        if (Objects.isNull(config)) {
            return null;
        }
        ConfigDefineEnum configDefine = ConfigDefineEnum.ofByKey(config.getConfigKey());
        String tenantId = String.valueOf(ConfigUtils.getTargetTenantId(configDefine));

        BaseConfigParam<?> baseConfigParam;
        synchronized (tenantId.intern()) {
            baseConfigParam = storageParamCache.get(tenantId);
            if (Objects.nonNull(baseConfigParam)) {
                return baseConfigParam;
            }
            ConfigKeyEnum configKey = ConfigKeyEnum.ofAndCheck(config.getConfigKey());
            baseConfigParam = BaseConfigParam.jsonToObject(configKey, config.getConfigValue());
            storageParamCache.put(tenantId, (StorageParam<?>) baseConfigParam);
        }
        return baseConfigParam;
    }

    @Override
    public void updateConfig(ConfigMsg msg) {
        log.info("delete config, tenantId: {}, data: {}", msg.getTenantId(), JSON.toJSONString(msg));
        storageParamCache.remove(String.valueOf(msg.getTenantId()));
    }

    public static String getTenantIdFromData(String data) {
        String tenantId = null;
        Matcher matcher = GET_FULL_URL_PATTERN.matcher(data);
        while (matcher.find()) {
            String url = matcher.group();
            if (!url.contains("tenantId")) {
                continue;
            }
            UrlBuilder urlBuilder = UrlBuilder.of(url);
            tenantId = (String) urlBuilder.getQuery().get("tenantId");
            if (StringUtils.isEmpty(tenantId)) {
                continue;
            }
            break;
        }
        return tenantId;
    }


    @Override
    public ConfigDefineEnum configDefine() {
        return ConfigDefineEnum.STORAGE;
    }

//    public static void main(String[] args) {
//        JSONObject data = new JSONObject();
//        for (int i = 0; i < 30; i++) {
//            data.put("test" + i, RandomUtil.randomString(200));
//        }
//
//        data.put("t1", "https://www.test.fi/crm/crm?tid=123123123123");
//        data.put("t2", "http://1.lingyang-boot.file/crm/crm?tid=123123123123");
//
//        JSONObject data1 = new JSONObject();
//        data1.put("t1", "https://www.test.fi/crm/crm?tid=123123123123");
//        data1.put("t2", "http://1.lingyang-boot.file/crm/crm?tenantId=123123123123 http://1.lingyang-boot.file/crm/crm?tenantId=123123123123");
//        data1.put("t4", "<p>hello123123 </p><p style=\"text-align: left;\"><img src=\"http://sw-minio.com:39000/lingyang-boot/data/2023/07/15/1/439280026968069.png\" alt=\"\" data-href=\"\" style=\"width: 237.50px;height: 237.50px;\"><img src=\"http://sw-minio.com:39000/lingyang-boot/image/2024/01/01/1/499611215903621.png\" alt=\"\" data-href=\"\" style=\"\"></p><p style=\"text-align: left;\"><br></p><p style=\"text-align: center;\"><br></p>");
//        data.put("t4", data1);
//        String str = JSON.toJSONString(data);
//
//        for (int i = 0; i < 20; i++) {
//            StopWatch started = StopWatch.createStarted();
//
//            System.out.println(getTenantIdFromData(str));
//            System.out.println(started.getTime(TimeUnit.MILLISECONDS));
//            started.stop();
//        }
//        System.out.println(str);
//
//    }
}
